;;======================================================================================================================
;;///// unpacker.asm /////////////////////////////////////////////////////////////////////////////////////// GPLv2 /////
;;======================================================================================================================
;; (c) 2006-2010 KolibriOS team <http://kolibrios.org/>
;;======================================================================================================================
;; This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
;; License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later
;; version.
;;
;; This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License along with this program. If not, see
;; <http://www.gnu.org/licenses/>.
;;======================================================================================================================

;-----------------------------------------------------------------------------------------------------------------------
kproc unpack ;//////////////////////////////////////////////////////////////////////////////////////////////////////////
;-----------------------------------------------------------------------------------------------------------------------
;# void __stdcall unpack(void* packed_data, void* unpacked_data);
;-----------------------------------------------------------------------------------------------------------------------
        pushad
        mov     esi, [esp + sizeof.regs_context32_t + 4]
        mov     edi, [esp + sizeof.regs_context32_t + 8]
        mov     eax, [esi + 8]
        and     al, 0xc0
        cmp     al, 0xc0
        jz      .failed
        mov     eax, [esi + 8]
        push    eax
        add     esi, 12
        and     al, not 0xc0
        dec     al
        jz      .lzma

  .failed:
        pop     eax
        popad
        ret     8

  .lzma:
        call    .lzma_unpack

  .common:
        pop     eax
        test    al, 0x80
        jnz     .ctr1
        test    al, 0x40
        jz      .ok
        lodsd
        mov     ecx, eax
        jecxz   .ok
        mov     dl, [esi]
        mov     esi, [esp + sizeof.regs_context32_t + 8]

  .c1:
        lodsb
        sub     al, 0xe8
        cmp     al, 1
        ja      .c1
        cmp     byte[esi], dl
        jnz     .c1
        lodsd
        ; "bswap eax" is not supported on i386
        shr     ax, 8
        ror     eax, 16
        xchg    al, ah
        sub     eax, esi
        add     eax, [esp + sizeof.regs_context32_t + 8]
        mov     [esi - 4], eax
        loop    .c1

  .ok:
        popad
        ret     8

  .ctr1:
        lodsd
        mov     ecx, eax
        jecxz   .ok
        mov     dl, [esi]
        mov     esi, [esp + sizeof.regs_context32_t + 8]

  .c2:
        lodsb

    @@: cmp     al, 0x0f
        jnz     .f
        lodsb
        cmp     al, 0x80
        jb      @b
        cmp     al, 0x90
        jb      @f

  .f:
        sub     al, 0xe8
        cmp     al, 1
        ja      .c2

    @@: cmp     byte[esi], dl
        jnz     .c2
        lodsd
        shr     ax, 8
        ror     eax, 16
        xchg    al, ah
        sub     eax, esi
        add     eax, [esp + sizeof.regs_context32_t + 8]
        mov     [esi - 4], eax
        loop    .c2
        jmp     .ok

  .lzma_unpack:

.pb                    = 2 ; pos state bits
.lp                    = 0 ; literal pos state bits
.lc                    = 3 ; literal context bits
.posStateMask          = (1 shl .pb) - 1
.literalPosMask        = (1 shl .lp) - 1

.kNumPosBitsMax        = 4
.kNumPosStatesMax      = 1 shl .kNumPosBitsMax

.kLenNumLowBits        = 3
.kLenNumLowSymbols     = 1 shl .kLenNumLowBits
.kLenNumMidBits        = 3
.kLenNumMidSymbols     = 1 shl .kLenNumMidBits
.kLenNumHighBits       = 8
.kLenNumHighSymbols    = 1 shl .kLenNumHighBits

.LenChoice             = 0
.LenChoice2            = 1
.LenLow                = 2
.LenMid                = .LenLow + (.kNumPosStatesMax shl .kLenNumLowBits)
.LenHigh               = .LenMid + (.kNumPosStatesMax shl .kLenNumMidBits)
.kNumLenProbs          = .LenHigh + .kLenNumHighSymbols

.kNumStates            = 12
.kNumLitStates         = 7
.kStartPosModelIndex   = 4
.kEndPosModelIndex     = 14
.kNumFullDistances     = 1 shl (.kEndPosModelIndex/2)
.kNumPosSlotBits       = 6
.kNumLenToPosStates    = 4
.kNumAlignBits         = 4
.kAlignTableSize       = 1 shl .kNumAlignBits
.kMatchMinLen          = 2

.IsMatch               = 0
.IsRep                 = .IsMatch + (.kNumStates shl .kNumPosBitsMax)
.IsRepG0               = .IsRep + .kNumStates
.IsRepG1               = .IsRepG0 + .kNumStates
.IsRepG2               = .IsRepG1 + .kNumStates
.IsRep0Long            = .IsRepG2 + .kNumStates
.PosSlot               = .IsRep0Long + (.kNumStates shl .kNumPosBitsMax)
.SpecPos               = .PosSlot + (.kNumLenToPosStates shl .kNumPosSlotBits)
.Align_                = .SpecPos + .kNumFullDistances - .kEndPosModelIndex
.Lencoder              = .Align_ + .kAlignTableSize
.RepLencoder           = .Lencoder + .kNumLenProbs
.Literal               = .RepLencoder + .kNumLenProbs

.LZMA_BASE_SIZE        = 1846 ; must be == Literal
.LZMA_LIT_SIZE         = 768

.kNumTopBits           = 24
.kTopValue             = 1 shl .kNumTopBits

.kNumBitModelTotalBits = 11
.kBitModelTotal        = 1 shl .kNumBitModelTotalBits
.kNumMoveBits          = 5

        push    edi
        ; int state=0;
        xor     ebx, ebx
        mov     [.previousByte], bl
        ; unsigned rep0=1,rep1=1,rep2=1,rep3=1;
        mov     eax, 1
        mov     edi, .rep0
        stosd
        stosd
        stosd
        stosd
        ; int len=0;
        ; result=0;
        mov     ecx, .Literal + (.LZMA_LIT_SIZE shl (.lc + .lp))
        mov     eax, .kBitModelTotal/2
        mov     edi, [.p]
        rep
        stosd
        ; RangeDecoderInit
        ; rd->ExtraBytes = 0
        ; rd->Buffer = stream
        ; rd->BufferLim = stream+bufferSize
        ; rd->Range = 0xFFFFFFFF
        pop     edi
        mov     ebp, [esi - 8] ; dest_length
        add     ebp, edi ; ebp = destination limit
        lodsd
        ; rd->code_ = eax
        mov     [.code_], eax
        or      [.range], -1

  .main_loop:
        cmp     edi, ebp
        jae     .main_loop_done
        mov     edx, edi
        and     edx, .posStateMask
        mov     eax, ebx
        shl     eax, .kNumPosBitsMax + 2
        lea     eax, [.IsMatch * 4 + eax + edx * 4]
        add     eax, [.p]
        call    .RangeDecoderBitDecode
        jc      .1
        movzx   eax, [.previousByte]

if .literalPosMask

        mov     ah, dl
        and     ah, .literalPosMask

end if

        shr     eax, 8 - .lc
        imul    eax, .LZMA_LIT_SIZE * 4
        add     eax, .Literal * 4
        add     eax, [.p]
        cmp     ebx, .kNumLitStates
        jb      .literal
        xor     edx, edx
        sub     edx, [.rep0]
        mov     dl, [edi + edx]
        call    .LzmaLiteralDecodeMatch
        jmp     @f

  .literal:
        call    .LzmaLiteralDecode

    @@: mov     [.previousByte], al
        stosb
        mov     al, bl
        cmp     bl, 4
        jb      @f
        mov     al, 3
        cmp     bl, 10
        jb      @f
        mov     al, 6

    @@: sub     bl, al
        jmp     .main_loop

  .1:
        lea     eax, [.IsRep * 4 + ebx * 4]
        add     eax, [.p]
        call    .RangeDecoderBitDecode
        jnc     .10
        lea     eax, [.IsRepG0 * 4 + ebx * 4]
        add     eax, [.p]
        call    .RangeDecoderBitDecode
        jc      .111
        mov     eax, ebx
        shl     eax, .kNumPosBitsMax + 2
        lea     eax, [.IsRep0Long * 4 + eax + edx * 4]
        add     eax, [.p]
        call    .RangeDecoderBitDecode
        jc      .1101
        cmp     bl, 7
        setae   bl
        lea     ebx, [9 + ebx + ebx]
        xor     edx, edx
        sub     edx, [.rep0]
        mov     al, [edi + edx]
        stosb
        mov     [.previousByte], al
        jmp     .main_loop

  .111:
        lea     eax, [.IsRepG1 * 4 + ebx * 4]
        add     eax, [.p]
        call    .RangeDecoderBitDecode
        mov     eax, [.rep1]
        jnc     .l3

  .l1:
        lea     eax, [.IsRepG2 * 4 + ebx * 4]
        add     eax, [.p]
        call    .RangeDecoderBitDecode
        mov     eax, [.rep2]
        jnc     .l2
        xchg    [.rep3], eax

  .l2:
        push    [.rep1]
        pop     [.rep2]

  .l3:
        xchg    eax, [.rep0]
        mov     [.rep1], eax

  .1101:
        mov     eax, .RepLencoder * 4
        add     eax, [.p]
        call    .LzmaLenDecode
        cmp     bl, 7
        setc    bl
        adc     bl, bl
        xor     bl, 3
        add     bl, 8
        jmp     .repmovsb

  .10:
        mov     eax, [.rep0]
        xchg    eax, [.rep1]
        xchg    eax, [.rep2]
        xchg    eax, [.rep3]
        cmp     bl, 7
        setc    bl
        adc     bl, bl
        xor     bl, 3
        add     bl, 7
        mov     eax, .Lencoder * 4
        add     eax, [.p]
        call    .LzmaLenDecode
        mov     eax, .kNumLenToPosStates - 1
        cmp     eax, ecx
        jb      @f
        mov     eax, ecx

    @@: push    ecx
        mov     ecx, .kNumPosSlotBits
        shl     eax, cl
        shl     eax, 2
        add     eax, .PosSlot * 4
        add     eax, [.p]
        call    .RangeDecoderBitTreeDecode
        mov     [.rep0], ecx
        cmp     ecx, .kStartPosModelIndex
        jb      .l6
        push    ecx
        mov     eax, ecx
        and     eax, 1
        shr     ecx, 1
        or      eax, 2
        dec     ecx
        shl     eax, cl
        mov     [.rep0], eax
        pop     edx
        cmp     edx, .kEndPosModelIndex
        jae     .l5
        sub     eax, edx
        shl     eax, 2
        add     eax, (.SpecPos - 1) * 4
        add     eax, [.p]
        call    .RangeDecoderReverseBitTreeDecode
        add     [.rep0], ecx
        jmp     .l6

  .l5:
        sub     ecx, .kNumAlignBits
        call    .RangeDecoderDecodeDirectBits
        mov     ecx, .kNumAlignBits
        shl     eax, cl
        add     [.rep0], eax
        mov     eax, .Align_ * 4
        add     eax, [.p]
        call    .RangeDecoderReverseBitTreeDecode
        add     [.rep0], ecx

  .l6:
        pop     ecx
        inc     [.rep0]
        jz      .main_loop_done

  .repmovsb:
        add     ecx, .kMatchMinLen
        push    esi
        mov     esi, edi
        sub     esi, [.rep0]
        rep
        movsb
        pop     esi
        mov     al, [edi - 1]
        mov     [.previousByte], al
        jmp     .main_loop

  .main_loop_done:
        ret


  .RangeDecoderBitDecode:
        ; in: eax->prob
        ; out: CF=bit; destroys eax
        push    edx
        mov     edx, [.range]
        shr     edx, .kNumBitModelTotalBits
        imul    edx, [eax]
        cmp     [.code_], edx
        jae     .ae
        mov     [.range], edx
        mov     edx, .kBitModelTotal
        sub     edx, [eax]
        shr     edx, .kNumMoveBits
        add     [eax], edx
        clc

  .n:
        lahf
        cmp     [.range], .kTopValue
        jae     @f
        shl     [.range], 8
        shl     [.code_], 8
        lodsb
        mov     byte[.code_], al

    @@: sahf
        pop     edx
        ret

  .ae:
        sub     [.range], edx
        sub     [.code_], edx
        mov     edx, [eax]
        shr     edx, .kNumMoveBits
        sub     [eax], edx
        stc
        jmp     .n

  .RangeDecoderDecodeDirectBits:
        ; in: ecx=numTotalBits
        ; out: eax=result; destroys edx
        xor     eax, eax

  .l:
        shr     [.range], 1
        shl     eax, 1
        mov     edx, [.code_]
        sub     edx, [.range]
        jb      @f
        mov     [.code_], edx
        or      eax, 1

    @@: cmp     [.range], .kTopValue
        jae     @f
        shl     [.range], 8
        shl     [.code_], 8
        push    eax
        lodsb
        mov     byte[.code_], al
        pop     eax

    @@: loop    .l
        ret

  .LzmaLiteralDecode:
        ; in: eax->probs
        ; out: al=byte; destroys edx
        push    ecx
        mov     ecx, 1

    @@: push    eax
        lea     eax, [eax + ecx * 4]
        call    .RangeDecoderBitDecode
        pop     eax
        adc     cl, cl
        jnc     @b

  .LzmaLiteralDecode.ret:
        mov     al, cl
        pop     ecx
        ret

  .LzmaLiteralDecodeMatch:
        ; in: eax->probs, dl=matchByte
        ; out: al=byte; destroys edx
        push    ecx
        mov     ecx, 1

  .LzmaLiteralDecodeMatch.1:
        add     dl, dl
        setc    ch
        push    eax
        lea     eax, [eax + ecx * 4 + 0x100 * 4]
        call    .RangeDecoderBitDecode
        pop     eax
        adc     cl, cl
        jc      .LzmaLiteralDecode.ret
        xor     ch, cl
        test    ch, 1
        mov     ch, 0
        jnz     @b
        jmp     .LzmaLiteralDecodeMatch.1

  .LzmaLenDecode:
        ; in: eax->prob, edx=posState
        ; out: ecx=len
        push    eax
        add     eax, .LenChoice * 4
        call    .RangeDecoderBitDecode
        pop     eax
        jnc     .0
        push    eax
        add     eax, .LenChoice2 * 4
        call    .RangeDecoderBitDecode
        pop     eax
        jc      @f
        mov     ecx, .kLenNumMidBits
        shl     edx, cl
        lea     eax, [eax + .LenMid * 4 + edx * 4]
        call    .RangeDecoderBitTreeDecode
        add     ecx, .kLenNumLowSymbols
        ret

    @@: add     eax, .LenHigh * 4
        mov     ecx, .kLenNumHighBits
        call    .RangeDecoderBitTreeDecode
        add     ecx, .kLenNumLowSymbols + .kLenNumMidSymbols
        ret

  .0:
        mov     ecx, .kLenNumLowBits
        shl     edx, cl
        lea     eax, [eax + .LenLow * 4 + edx * 4]

  .RangeDecoderBitTreeDecode:
        ; in: eax->probs,ecx=numLevels
        ; out: ecx=length; destroys edx
        push    ebx
        mov     edx, 1
        mov     ebx, edx

    @@: push    eax
        lea     eax, [eax + edx * 4]
        call    .RangeDecoderBitDecode
        pop     eax
        adc     dl, dl
        add     bl, bl
        loop    @b
        sub     dl, bl
        pop     ebx
        mov     ecx, edx
        ret

  .RangeDecoderReverseBitTreeDecode:
        ; in: eax->probs,ecx=numLevels
        ; out: ecx=length; destroys edx
        push    ebx ecx
        mov     edx, 1
        xor     ebx, ebx

    @@: push    eax
        lea     eax, [eax + edx * 4]
        call    .RangeDecoderBitDecode
        lahf
        adc     edx, edx
        sahf
        rcr     ebx, 1
        pop     eax
        loop    @b
        pop     ecx
        rol     ebx, cl
        mov     ecx, ebx
        pop     ebx
        ret
kendp

uglobal
  align 4
; unpack.p            rd unpack.LZMA_BASE_SIZE + (unpack.LZMA_LIT_SIZE shl (unpack.lc + unpack.lp))
  unpack.p            dd ?
  unpack.code_        dd ?
  unpack.range        dd ?
  unpack.rep0         dd ?
  unpack.rep1         dd ?
  unpack.rep2         dd ?
  unpack.rep3         dd ?
  unpack.previousByte db ?
endg
